﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Text.RegularExpressions;
using WMM.Tapioca.Instance;
using WMM.Tapioca.Taxonomy;
using WMM.Tapioca.Linkbase;
using WMM.Tapioca.Utilities;
using WMM.Tapioca.XbrlDimension;

namespace WMM.Tapioca.Formula.Filters
{
    /// <summary>
    /// The explicit dimension filter can be used to match facts with any one of the domain members
    /// in an explicit dimension domain as the value for that explicit dimension.
    /// </summary>
    public class ExplicitDimensionFilter : Filter
    {
        private string dimension;

        public string Dimension
        {
            get { return dimension; }
            set { dimension = value; }
        }

        //private XName dimension;

        ///// <returns> Dimension. </returns>
        //public XName Dimension
        //{
        //    get { return dimension; }
        //    set { dimension = value; }
        //}

        private List<MemberModel> membersModel;

        /// <returns> Members. </returns>
        public List<MemberModel> MembersModel
        {
            get { return membersModel; }
            set { membersModel = value; }
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        public ExplicitDimensionFilter(Resource resource, VariableSet variableSet)
            : base(resource)
        {
            membersModel = new List<MemberModel>();

            XElement dimensionX = resource.ResourceXElement.Descendants().Where(d => d.Parent.Name.LocalName.Equals("dimension") &&
            d.Name.LocalName.Equals("qname")).FirstOrDefault();

            if (dimensionX != null)
            {
                string prefix = dimensionX.Value.GetPrefix();
                string localName = dimensionX.Value.GetLocalName();

                XNamespace ns = resource.ExtendedLinkParent.ParentDocument.ThisXDocument.Root.GetNamespaceOfPrefix(prefix);

                //this.dimension = ns + localName;

                this.dimension = Regex.Replace(dimensionX.Value, @"\s+", "");
            }

            XElement qnameExpression = resource.ResourceXElement.Descendants().Where(d => d.Parent.Name.LocalName.Equals("dimension") &&
            d.Name.LocalName.Equals("qnameExpression")).FirstOrDefault();

            if (qnameExpression != null)
            {
                string expression = qnameExpression.Value;
                expression = expression.TrimEnd().TrimStart();
                expression = Regex.Replace(expression, @"\s+", " ");
                dimension = EvaluateExpression(expression);
            }

            List<XElement> membersXElement = resource.ResourceXElement.Descendants().Where(d => d.Name.LocalName.Equals("member")).ToList();

            if (membersXElement.Count() > 0)
            {
                foreach (XElement memberElement in membersXElement)
                {
                    MemberModel memberModel = new MemberModel();

                    XElement variableM = memberElement.Elements().Where(d => d.Name.LocalName.Equals("variable")).FirstOrDefault();

                    if (variableM != null)
                    {
                        memberModel.Variable = variableM.Value;
                    }

                    XElement qnameExpressionM = memberElement.Elements().Where(d => d.Name.LocalName.Equals("qnameExpression")).FirstOrDefault();

                    if (qnameExpressionM != null)
                    {
                        string expression = qnameExpressionM.Value;
                        expression = expression.TrimEnd().TrimStart();
                        expression = Regex.Replace(expression, @"\s+", " ");
                        memberModel.QnameExpression = expression;
                    }

                    XElement qnameM = memberElement.Elements().Where(d => d.Name.LocalName.Equals("qname")).FirstOrDefault();

                    if (qnameM != null)
                    {
                        memberModel.QName = qnameM.Value;
                    }

                    XElement linkrole = memberElement.Elements().Where(d => d.Name.LocalName.Equals("linkrole")).FirstOrDefault();

                    if (linkrole != null)
                    {
                        memberModel.Linkrole = linkrole.Value;
                    }

                    XElement arcrole = memberElement.Elements().Where(d => d.Name.LocalName.Equals("arcrole")).FirstOrDefault();

                    if (arcrole != null)
                    {
                        memberModel.Arcrole = arcrole.Value;
                    }

                    XElement axis = memberElement.Elements().Where(d => d.Name.LocalName.Equals("axis")).FirstOrDefault();

                    if (axis != null)
                    {
                        memberModel.Axis = axis.Value;
                    }

                    membersModel.Add(memberModel);
                }                
            }
        }


        public override List<FactItem> EvaluateFilter(VariableSet variableSet, List<FactItem> itemsToFilter, DTS dts)
        {
            this.dts = dts;

            List<FactItem> itemListTemp = new List<FactItem>();

            string thisVariableName = GetVariableName(this.Label, variableSet);

            List<string> dependecyVariables = GetDependecyVariables(variableSet, thisVariableName);

            if (MembersModel.Count > 0)
            {
                foreach (MemberModel memberModel in MembersModel)
                {
                    if (memberModel.Linkrole != null && memberModel.Arcrole != null && memberModel.Axis != null)
                    {
                        HashSet<Arc> networkRelationship = GetNetworkRelationship(dts, memberModel);
                       
                        foreach (FactItem itemToFilter in itemsToFilter)
                        {
                            Context context = itemToFilter.ContextRef;

                            List<XElement> explicitMembers = context.ExplicitMembers;

                            foreach (XElement explicitMember in context.ExplicitMembers)
                            {
                                string dimensionQName = (string)explicitMember.Attribute("dimension") ?? string.Empty;

                                if (memberModel.QnameExpression != null)
                                {
                                    memberModel.QName = EvaluateExpression(dependecyVariables, variableSet, memberModel.QnameExpression);
                                }

                                if (memberModel.Variable != null)
                                {
                                    List<FactItem> factVariableItems = variableSet.variableItems[memberModel.Variable];

                                    foreach (FactItem item in factVariableItems)
                                    {
                                        memberModel.QName = GetDimensionValue(item, Dimension);

                                        if (memberModel.QName != null)
                                        {
                                            Concept rootNode = dts.GetConceptByName(memberModel.QName.GetLocalName());

                                            Locator locator = GetLocator(rootNode.Id, networkRelationship);

                                            if (locator != null)
                                            {
                                                if (Complement ? !(dimensionQName.GetLocalName().Equals(Dimension.GetLocalName()) && IsAxisSatisfied(memberModel, locator, rootNode, itemToFilter, explicitMember.Value)) :
                                                    dimensionQName.GetLocalName().Equals(Dimension.GetLocalName()) && IsAxisSatisfied(memberModel, locator, rootNode, itemToFilter, explicitMember.Value))
                                                {
                                                    itemListTemp.Add(itemToFilter);
                                                    break;
                                                }
                                            }
                                        }
                                    }
                                }
                                else // QName or QnameExpression
                                {
                                    Concept rootNode2 = dts.GetConceptByName(memberModel.QName.GetLocalName());

                                    Locator locator2 = GetLocator(rootNode2.Id, networkRelationship);

                                    if (locator2 != null)
                                    {
                                        if (Complement ? !(dimensionQName.GetLocalName().Equals(Dimension.GetLocalName()) && IsAxisSatisfied(memberModel, locator2, rootNode2, itemToFilter, explicitMember.Value)) :
                                            dimensionQName.GetLocalName().Equals(Dimension.GetLocalName()) && IsAxisSatisfied(memberModel, locator2, rootNode2, itemToFilter, explicitMember.Value))
                                        {
                                            itemListTemp.Add(itemToFilter);
                                            break;
                                        }
                                    }
                                }
                            }
                        }

                        return itemListTemp;
                    }
                    else // role, arcrole and axis are null
                    {
                        if (memberModel.Variable != null)
                        {
                            List<FactItem> factVariableItems = variableSet.variableItems[memberModel.Variable];

                            foreach (FactItem item in factVariableItems)
                            {
                                memberModel.QName = GetDimensionValue(item, Dimension);

                                foreach (FactItem itemToFilter in itemsToFilter)
                                {
                                    Context context = itemToFilter.ContextRef;
                                    List<XElement> explicitMembers = context.ExplicitMembers;

                                    foreach (XElement explicitMember in context.ExplicitMembers)
                                    {
                                        string dimensionQName = (string)explicitMember.Attribute("dimension") ?? string.Empty;

                                        if (Complement ? !(dimensionQName.GetLocalName().Equals(Dimension.GetLocalName()) && memberModel.QName.GetLocalName().Equals(explicitMember.Value.GetLocalName())) :
                                            dimensionQName.GetLocalName().Equals(Dimension.GetLocalName()) && memberModel.QName.GetLocalName().Equals(explicitMember.Value.GetLocalName()))
                                        {
                                            itemListTemp.Add(itemToFilter);
                                            break;
                                        }
                                    }
                                }
                            }

                            return itemListTemp;
                        }

                        // QName or QnameExpression

                        if (memberModel.QnameExpression != null)
                        {
                            memberModel.QName = EvaluateExpression(dependecyVariables, variableSet, memberModel.QnameExpression);
                        }

                        foreach (FactItem itemToFilter in itemsToFilter)
                        {
                            Context context = itemToFilter.ContextRef;
                            List<XElement> explicitMembers = context.ExplicitMembers;

                            if (context.ExplicitMembers.Count == 0)
                            {
                                string defaultDimensionValue = GetDimensionValueDefault(dts, itemToFilter);

                                if (Complement ? !(memberModel.QName.GetLocalName().Equals(defaultDimensionValue)) :
                                        memberModel.QName.GetLocalName().Equals(defaultDimensionValue))
                                {
                                    itemListTemp.Add(itemToFilter);
                                }
                            }
                            else
                            {
                                foreach (XElement explicitMember in context.ExplicitMembers)
                                {
                                    string dimensionQName = (string)explicitMember.Attribute("dimension") ?? string.Empty;

                                    if (Complement ? !(dimensionQName.GetLocalName().Equals(Dimension.GetLocalName()) && memberModel.QName.Equals(explicitMember.Value)) :
                                        dimensionQName.GetLocalName().Equals(Dimension.GetLocalName()) && memberModel.QName.Equals(explicitMember.Value))
                                    {
                                        itemListTemp.Add(itemToFilter);
                                        break;
                                    }
                                }
                            }
                        }
                    }
                } // foreach (MemberModel memberModel in MembersModel)
            }
            else
            {  // only dimension
                foreach (FactItem item in itemsToFilter)
                {
                    Context context = item.ContextRef;

                    if (!Complement && HasExplicitDimension(item, context))
                    {
                        itemListTemp.Add(item);
                    }

                    if (Complement && !HasExplicitDimension(item, context))
                    {
                        itemListTemp.Add(item);
                    }
                }
            }

            return itemListTemp;
        }

        private static HashSet<Arc> GetNetworkRelationship(DTS dts, MemberModel memberModel)
        {
            foreach (Tuple<string, string, string, string> tuple in dts.NetworkRelationships.Keys)
            {
                if (tuple.Item2.Equals(memberModel.Linkrole) && tuple.Item4.Equals(memberModel.Arcrole))
                {
                    return dts.NetworkRelationships[tuple];
                }
            }
            return null;
        }


        private bool HasExplicitDimension(FactItem item, Context context)
        {
            if (context.ExplicitMembers.Count == 0)
            {
                string dimensionLocalName = GetDimensionDefault(dts, item);

                if (dimensionLocalName.Equals(Dimension))
                {
                    return true;
                }
            }

            foreach (XElement explicitMember in context.ExplicitMembers)
            {
                string dimensionQName = (string)explicitMember.Attribute("dimension") ?? string.Empty;

                if (dimensionQName.Equals(Dimension))
                {
                    return true;
                }
            }

            return false;
        }


        private string GetDimensionDefault(DTS dts, FactItem item)
        {
            string defaultDimension = "";

            foreach (Inheritance inheritance in dts.DtsDimension.Inheritances)
            {
                foreach (Concept conceptInherited in inheritance.PrimaryItemsInherited)
                {
                    if (inheritance.PrimaryItem.Id.Equals(item.Concept.Id) || conceptInherited.Id.Equals(item.Concept.Id))
                    {
                        foreach (Dimension dimensionDRS in dts.DtsDimension.Dimensions)
                        {
                            if (dimensionDRS.GetName().LocalName == Dimension.GetLocalName())
                            {
                                defaultDimension = Dimension;
                            }
                        }
                    }
                }
            }

            return defaultDimension;
        }


        private string GetDimensionValue(FactItem item, string Dimension)
        {
            foreach (XElement explicitMember in item.ContextRef.ExplicitMembers)
            {
                string dimensionQName = (string)explicitMember.Attribute("dimension") ?? string.Empty;

                string prefix = dimensionQName.GetPrefix();
                string localName = dimensionQName.GetLocalName();

                XNamespace ns = explicitMember.GetNamespaceOfPrefix(prefix);

                if (dimensionQName.GetLocalName().Equals(Dimension.GetLocalName()))
                {
                    return explicitMember.Value;
                }
            }

            string defaultDimensionValue = GetDimensionValueDefault(dts, item);

            return defaultDimensionValue;
        }


        private string GetDimensionValueDefault(DTS dts, FactItem item)
        {
            string defaultDimensionValue = "";

            // PrimaryItemsInherited
            foreach (Inheritance inheritance in dts.DtsDimension.Inheritances)
            {
                foreach (Concept conceptInherited in inheritance.PrimaryItemsInherited)
                {
                    if (inheritance.PrimaryItem.Id.Equals(item.Concept.Id) || conceptInherited.Id.Equals(item.Concept.Id))
                    {
                        foreach (Dimension dimension in dts.DtsDimension.Dimensions)
                        {
                            if (dimension.GetName().LocalName == Dimension.GetLocalName())
                            {
                                defaultDimensionValue = dimension.DefaultConcept.Name;
                            }
                        }  
                    }
                }
            }

            return defaultDimensionValue;
        }



        public Locator GetLocator(string id, HashSet<Arc> networkRelationship)
        {
            foreach (Arc arc in networkRelationship)
            {
                if (arc.FromLocator.HrefResourceId.Equals(id))
                {
                    return arc.FromLocator;                    
                }
                
                if (arc.ToLocator.HrefResourceId.Equals(id))
                {
                    return arc.ToLocator;                    
                }
            }

            return null;
        }
    }

}
